﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DaemonBase.cs" company="">
//   
// </copyright>
// <summary>
//   Defines the <see cref="DaemonBase" /> class.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Resharper.Abstractions.Daemons
{
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Linq;
  using JetBrains.Annotations;
  using JetBrains.Application.Settings;
  using JetBrains.DocumentModel;
  using JetBrains.ReSharper.Daemon;
  using JetBrains.ReSharper.Psi;
  using JetBrains.ReSharper.Psi.CSharp;
  using JetBrains.ReSharper.Psi.CSharp.Tree;
  using JetBrains.ReSharper.Psi.Tree;
  using JetBrains.Util;
  using Resharper.Abstractions.CodeDom.TreeNodes;

  /// <summary>
  /// Defines the <see cref="DaemonBase"/> class.
  /// </summary>
  public abstract class DaemonBase : IDaemonStage, IDaemonStageProcess
  {
    #region Constants and Fields

    /// <summary>
    /// The empty parameters field.
    /// </summary>
    private static readonly object[] emptyParameters = new object[0];

    #endregion

    #region Explicit Interface Properties

    /// <summary>
    /// Whole daemon process
    /// </summary>
    IDaemonProcess IDaemonStageProcess.DaemonProcess
    {
      get
      {
        return this.CurrentProcess;
      }
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets or sets the current process.
    /// </summary>
    /// <value>
    /// The current process.
    /// </value>
    internal IDaemonProcess CurrentProcess { get; set; }

    /// <summary>
    /// Gets or sets the highlights.
    /// </summary>
    /// <value>
    /// The highlights.
    /// </value>
    internal ICollection<HighlightBase> Highlights { get; set; }

    #endregion

    #region Public Methods and Operators

    /// <summary>
    /// Creates the highlight.
    /// </summary>
    /// <typeparam name="T">
    /// </typeparam>
    /// <returns>
    /// Returns the highlight.
    /// </returns>
    [NotNull]
    public T CreateHighlight<T>() where T : HighlightBase, new()
    {
      var result = new T();

      this.Highlights.Add(result);

      return result;
    }

    #endregion

    #region Explicit Interface Methods

    /// <summary>
    /// Creates a code analysis process corresponding to this stage for analysing a file.
    /// </summary>
    /// <param name="process">
    /// The process.
    /// </param>
    /// <param name="settings">
    /// The settings.
    /// </param>
    /// <param name="processKind">
    /// Kind of the process.
    /// </param>
    /// <returns>
    /// Code analysis process to be executed or <c>null</c> if this stage is not available for this file.
    /// </returns>
    IEnumerable<IDaemonStageProcess> IDaemonStage.CreateProcess(IDaemonProcess process, IContextBoundSettingsStore settings, DaemonProcessKind processKind)
    {
      var constructor = this.GetType().GetConstructor(Type.EmptyTypes);
      Debug.Assert(constructor != null, "constructor != null");

      var result = (DaemonBase)constructor.Invoke(emptyParameters);

      result.CurrentProcess = process;
      result.Settings = settings;

      yield return result;
    }

    /// <summary>
    /// Gets the settings.
    /// </summary>
    protected IContextBoundSettingsStore Settings { get; private set; }

    /// <summary>
    /// Check the error stripe indicator necessity for this stage after processing given <paramref name="sourceFile"/>
    /// </summary>
    /// <param name="sourceFile">
    /// The source file.
    /// </param>
    /// <param name="settingsStore">
    /// The settings store.
    /// </param>
    /// <returns>
    /// Returns the error stripe.
    /// </returns>
    ErrorStripeRequest IDaemonStage.NeedsErrorStripe(IPsiSourceFile sourceFile, IContextBoundSettingsStore settingsStore)
    {
      return ErrorStripeRequest.STRIPE_AND_ERRORS;
    }

    /// <summary>Executes the process.
    /// The process should check for <see cref="P:JetBrains.ReSharper.Daemon.IDaemonProcess.InterruptFlag"/> periodically (with intervals less than 100 ms)
    /// and throw <see cref="T:JetBrains.Application.Progress.ProcessCancelledException"/> if it is true.
    /// Failing to do so may cause the program to prevent user from typing while analysing the code.
    /// Stage results should be passed to
    /// . If DaemonStageResult is <c>null</c>, it means that no highlightings available</summary>
    /// <param name="commiter">The commiter.</param>
    void IDaemonStageProcess.Execute(Action<DaemonStageResult> commiter)
    {
      var sourceFile = this.CurrentProcess.SourceFile;

      if (!this.CanExecute(new SourceFile(sourceFile)))
      {
        return;
      }

      var psiManager = PsiManager.GetInstance(this.CurrentProcess.Solution);

      var files = psiManager.GetPsiFiles<CSharpLanguage>(sourceFile);
      if (files.IsNullOrEmpty())
      {
        return;
      }

      var file = files.First();

      this.PreExecute(file, this.Settings);

      this.Highlights = new List<HighlightBase>();

      var processor = new RecursiveElementProcessor(this.ProcessTreeNode);
      processor.Process(file);

      var infos = new List<HighlightingInfo>();

      foreach (var highlightBase in this.Highlights)
      {
        highlightBase.InternalDocument = this.CurrentProcess.Document;

        var documentRange = new DocumentRange(this.CurrentProcess.Document, new TextRange(highlightBase.Range.Start, highlightBase.Range.End));

        var info = new HighlightingInfo(documentRange, highlightBase);

        infos.Add(info);
      }

      commiter(new DaemonStageResult(infos));
    }

    #endregion

    #region Methods

    /// <summary>
    /// Determines whether this instance can execute the specified source file.
    /// </summary>
    /// <param name="sourceFile">
    /// The source file.
    /// </param>
    /// <returns>
    /// <c>true</c> if this instance can execute the specified source file; otherwise, <c>false</c>.
    /// </returns>
    protected virtual bool CanExecute(SourceFile sourceFile)
    {
      return true;
    }

    /// <summary>Pres the execute.</summary>
    /// <param name="file">The file.</param>
    /// <param name="settings">The settings.</param>
    protected virtual void PreExecute(IFile file, IContextBoundSettingsStore settings)
    {
    }

    /// <summary>
    /// Processes the specified tree node.
    /// </summary>
    /// <param name="treeNode">
    /// The tree node.
    /// </param>
    protected abstract void Process(TreeNode treeNode);

    /// <summary>
    /// Processes the tree node.
    /// </summary>
    /// <param name="treeNode">
    /// The tree node.
    /// </param>
    protected virtual void ProcessTreeNode(ITreeNode treeNode)
    {
      try
      {
        this.Process(new TreeNode(treeNode));
      }
      catch
      {
        return;
      }
    }

    #endregion
  }
}